Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 7 Weitere Möglichkeiten von C#
  gp 7.1 Operatorüberladung
    gp 7.1.1 Die Syntax der Operatorüberladung
    gp 7.1.2 Beispiel einer Operatorüberladung
    gp 7.1.3 Überladungsbeispiele
    gp 7.1.4 Benutzerdefinierte Konvertierungen – implizit und explizit
  gp 7.2 Indexer
    gp 7.2.1 Überladen von Indexern
    gp 7.2.2 Parameterbehaftete Eigenschaften
  gp 7.3 Collections (Auflistungen)
    gp 7.3.1 Die elementaren Schnittstellen der Auflistungsklassen
    gp 7.3.2 Die Klasse »ArrayList«
    gp 7.3.3 Das Sortieren der Elemente einer »ArrayList«
    gp 7.3.4 Die Schnittstelle »IDictionary«
    gp 7.3.5 Die Klasse »Hashtable«
    gp 7.3.6 Die Klassen »Queue« und »Stack«
    gp 7.3.7 Objektauflistungen im Überblick
    gp 7.3.8 Benutzerdefinierte Auflistungen
  gp 7.4 Generics – Generische Datentypen
    gp 7.4.1 Die Typproblematik am Beispiel der Klasse »Stack«
    gp 7.4.2 Die Lösung mit einer generischen Klasse
    gp 7.4.3 Typparameter mit Constraints einschränken
    gp 7.4.4 Generische Methoden
    gp 7.4.5 Generics und Vererbung
    gp 7.4.6 Konvertierung von Generics
    gp 7.4.7 Generische Delegate
    gp 7.4.8 Generische Klassen in der .NET-Klassenbibliothek
    gp 7.4.9 Eigene Auflistungen mit »yield« durchlaufen
    gp 7.4.10 Daten durch »null« beschreiben
  gp 7.5 Fortgeschrittene Delegat-Techniken
    gp 7.5.1 Multicast-Delegate
  gp 7.6 Attribute
    gp 7.6.1 Das »Flags«-Attribut
    gp 7.6.2 Anmerkungen zu den Attributen
    gp 7.6.3 Benutzerdefinierte Attribute
  gp 7.7 Unsicherer Programmcode – Zeigertechnik in C#
    gp 7.7.1 Das Schlüsselwort »unsafe«
    gp 7.7.2 Die Deklaration von Zeigern
    gp 7.7.3 Die »fixed«-Anweisung
    gp 7.7.4 Zeigerarithmetik
    gp 7.7.5 Der Operator »->«


Galileo Computing

7.6 Attribute  downtop

Ein Attribut ist ein Feature von .NET, das einer Komponente Zusatzinformationen ähnlich einem Spickzettel bereitstellt oder ein bestimmtes Verhalten signalisiert oder vorgibt. Attribute werden zur Laufzeit ausgewertet. Dieses Prinzip ist grundsätzlich nicht neu, denken Sie nur an die altbekannten INI-Dateien, die eine ähnliche Zielsetzung hatten. Mit einem Attribut lässt sich das Laufzeitverhalten praktisch aller .NET-Elemente beeinflussen: Assemblies, Klassen, Interfaces, Strukturen, Delegate, Enumerationen, Konstruktoren, Methoden, Eigenschaften, Parameter, Ereignisse, ja sogar die Rückgabewerte von Methoden.

Als Basisklasse aller Attribute dient die abstrakte Klasse Attribute aus dem Namespace System. Schaut man in die Online-Dokumentation zu Attribute, wird man feststellen, dass das .NET Framework sehr viele Attribute vordefiniert, die aus Attribute abgeleitet sind. Alle denkbaren Anforderungen werden damit sicherlich nicht abgedeckt, deshalb können Sie auch benutzerdefinierte Attribute entwickeln und dadurch die Flexibilität Ihrer Anwendung erhöhen.


Galileo Computing

7.6.1 Das »Flags«-Attribudowntop

Wir wollen uns die Wirkungsweise eines Attributs exemplarisch am Attribut FlagsAttribute ansehen, das vom .NET Framework bereitgestellt wird und mit Enumeration verknüpft ist. Dazu nehmen wir zunächst an, dass eine Enumeration namens Keys die drei Zustandstasten (Shift), (Strg) und (Alt) beschreibt.


public enum Keys {
  Shift = 1,
  Ctrl = 2,
  Alt = 4
}

Wird diese Enumeration um FlagsAttribute erweitert, geben wir an, dass die Aufzählung als Kombination von Bits aufgefasst werden kann.


[FlagsAttribute] public enum Keys {
   Shift = 1,
   Ctrl = 2,
   Alt = 4
}

Wir können nun eine Variable vom Typ Keys deklarieren und ihr einen Wert zuweisen, der den Zustand der beiden gleichzeitig gedrückten Tasten (Strg) und (Shift) beschreibt. Beide verknüpfen wir mit dem »|«-Operator:


Keys tastenkombination = Keys.Ctrl | Keys.Shift;

Mit den bitweisen Operatoren kann nun natürlich auch geprüft werden, ob der Anwender eine bestimmte Tastenkombination gedrückt hat. Lassen wir uns nun den Inhalt der Variablen tastenkombination mit


Console.WriteLine(tastenkombination.ToString());

unter Aufruf von ToString an der Konsole ausgeben, erhalten wir:


Shift, Ctrl

Hätten wir FlagsAttribut nicht gesetzt, würde die Ausgabe 3 lauten.

Sie müssen berücksichtigen, dass die Mitglieder solchermaßen definierter Enumerationen Zweierpotenzen sind (also 1, 2, 4, 8, 16, 32, 64 ...). Alternativ sind auch hexadezimale Zahlenwerte zulässig.


Hinweis   Wird ein Attribut mit einem Element verknüpft, darf auf das Suffix Attribute verzichtet werden. Bemerkt der Compiler die Verknüpfung eines Attributs mit einem Element, sucht er nach einer Klasse, die mit dem angegebenen Attributbezeichner übereinstimmt und gleichzeitig die Klasse Attribute ableitet. Wird er nicht fündig, hängt der Compiler automatisch das Suffix Attribute dem Bezeichner an und wiederholt seine Suche.


Galileo Computing

7.6.2 Anmerkungen zu den Attributen  downtop

Das Attribut [Flags] erinnert in seiner Wirkungsweise eher an eine boolesche Variable, die gesetzt ist oder auch nicht. Weist eine Enumeration dieses Attribut auf, lassen sich die Elemente der Aufzählung bitweise miteinander kombinieren. Sehr ähnlich verhalten sich auch viele andere Attribute der .NET-Klassenbibliothek. Das Attribut [Serializable] kennzeichnet beispielsweise eine Klasse als binär serialisierbar. Darüber werden Sie in Kapitel 13 noch mehr erfahren.

Jedes .NET-Projekt weist neben den Quellcodedateien auch die Datei AssemblyInfo.cs auf, die Metadaten über die Assemblierung enthält. Dazu gehören Versionsinformationen, Titel und Copyright-Hinweise. Diese Zusatzinformationen werden ebenfalls über Attribute bereitgestellt und können direkt in AssemblyInfo.cs eingetragen werden. Die Daten werden zur Laufzeit über Reflection ausgewertet, wie auch die aller anderen Attribute.

Attribute basieren auf Klassendefinitionen und können daher alle klassentypischen Elemente enthalten. Dazu gehören neben Konstruktoren auch Felder. Insbesondere diese beiden Elemente ermöglichen es, über ein Attribut dem attributierten Element Zusatzinformationen bereitzustellen. Wie das in der Praxis aussieht, machen wir uns am Beispiel eines benutzerdefinierten Attributs deutlich.


Galileo Computing

7.6.3 Benutzerdefinierte Attributtoptop

Die erste Überlegung bei der Entwicklung eines benutzerdefinierten Attributs sollte sein, ob das Attribut nur auf die spezielle Anwendung beschränkt bleibt oder anwendungsübergreifend seine Funktionalität anbieten soll. Gehen wir von letzterem Fall aus, müssen wir die Attributdefinition in einer Klassenbibliothek bereitstellen, die nach der Kompilierung über Verweise von einer anderen Anwendung eingebunden werden kann.

Das folgende Codefragment zeigt eine typische Attributdefinition:


[AttributeUsage(AttributeTargets.All, 
                Inherited = false,  
                AllowMultiple = false)] 
public class UserDefinedAttribute : Attribute { }

Das ist schon alles! Unser Attribut soll nur wie eine boolesche Variable operieren, die gesetzt ist oder nicht. Daher benötigen wir in diesem Fall auch keinen weiteren Programmcode.

Zwei Fakten sind ausschlaggebend, um aus einer Klassendefinition ein Attribut zu machen:

1.  Der Definition eines benutzerdefinierten Attributs selbst geht immer die Definition des Attributs AttributeUsageAttribute voraus.
2. Die Klassendefinition wird aus Attribute abgeleitet.
       

Die Voranstellung eines Attributs mit einer Parameterliste vor der eigentlichen Attributdefinition hat den Grund, hier bereits elementare Eigenschaften der neuen Attributklasse festzulegen. In diesem Zusammenhang sind drei Parameter besonders interessant:

gp  AttributeTargets
gp  Inherited
gp  AllowMultiple

Während AttributeTargets angegeben werden muss, sind die beiden anderen optional.

AttributeTargets

Jedes Attribut kann sich nur auf bestimmte Codeelemente auswirken. Diese werden mit AttributeTargets bekannt geben. Im Beispiel oben wird mit AttributeTargets.All zum Ausdruck gebracht, dass wir das benutzerdefinierte Attribut auf alle Elemente anwenden können, aber das muss nicht immer so sein. Man kann den Einsatz eines Attributs ebenso gut nur auf Methoden oder Felder beschränken. Es steht dabei immer die Frage im Vordergrund: Was soll das Attribut letztendlich bewirken, welche Elemente sollen über das Attribut beeinflusst werden?

AttributeTargets ist in der .NET-Klassenbibliothek als Enumeration vordefiniert und enthält die in der folgenden Tabelle aufgeführten Entitäten.


Tabelle 7.7   Mitglieder der »AttributeTargets«-Enumeration

Mitglieder Beschreibung
All Das Attribut gilt für jedes Element der Anwendung.
Assembly Attribut gilt für die Assemblierung.
Class Attribut gilt für die Klasse.
Constructor Attribut gilt für den Konstruktor.
Delegate Attribut gilt für den Delegat.
Enum Attribut gilt für die Enumeration.
Event Attribut gilt für das Ereignis.
Field Attribut gilt für das Feld.
Interface Attribut gilt für die Schnittstelle.
Method Attribut gilt für die Methode.
Module Attribut gilt für das Modul.
Parameter Attribut gilt für den Parameter.
Property Attribut gilt für die Property.
ReturnValue Attribut gilt für den Rückgabewert.
Struct Attribut gilt für die Struktur.

Natürlich steht auch hinter AttributeUsageAttribute eine Klassendefinition, die im Namespace System zu finden ist. Im Grunde genommen unterscheidet sich eine Klasse, die ein Attribut beschreibt, nicht von einer herkömmlichen Klasse. Daher verwundert es nicht, dass die Klasse AttributeUsageAttribute einen Konstruktor definiert, der einen Parameter vom Typ AttributeTargets erwartet:


public AttributeUsageAttribute(AttributeTargets validon);

Der Parameter validon vom Typ AttributeTargets wird bitweise interpretiert. Jedes Bit beschreibt dabei ein Element, auf welches das Attribut angewendet werden kann. Möchte man das Attribut mehreren verschiedenen Elementen zugänglich machen, muss man mehrere AttributeTargets-Konstanten bitweise verknüpfen, z.B.:


[AttributeUsage(AttributeTargets.Method |  AttributeTargets.Property)]

In diesem Fall wird ein Attribut bereitgestellt, das sowohl Methoden als auch Eigenschaften als Informationsquelle dienen kann.

Inherited

Eine Klasse kann ihre Mitglieder einer abgeleiteten Klasse vererben. Einem Entwickler stellt sich natürlich die Frage, ob das Attribut in den Vererbungsprozess mit einbezogen wird oder ob es Gründe gibt, es davon auszuschließen. Einem benutzerdefiniertem Attribut teilen wir dies durch den booleschen Parameter Inherited mit, den wir optional AttributeUsageAttribute übergeben können. Standardmäßig ist der Wert auf true festgelegt, demnach vererbt sich ein gesetztes Attribut in einer Vererbungshierarchie weiter.

AllowMultiple

In wohl eher seltenen Fällen kann es erforderlich sein, ein Attribut demselben Element mehrfach zuzuweisen. Diese Situation wäre denkbar, wenn man über das Attribut einem Element mehrere Feldinformationen zukommen lassen möchte. Dann muss man die mehrfache Anwendung eines Attributs explizit gestatten. Zur Lösung geben Sie den Parameter


AllowMultiple = true

an. Verzichten Sie auf diese Angabe, kann ein Attribut per Definition mit einem bestimmten Element nur einmal verknüpft werden.

Konstruktoren eines Attributs

Weiter oben haben Sie bereits erfahren, dass es möglich ist, in einer Attributklasse einen Konstruktor zu definieren. Im folgenden Beispiel ist das Attribut DeveloperAttribute definiert, das zwei Datenmember enthält, von denen einer durch den Konstruktoraufruf initialisiert wird. Durch AttributeTargets geben wir bekannt, dass das Attribut jedem Codeelement angeheftet werden kann.


[AttributeUsage(AttributeTargets.All)] 
public class DeveloperAttribute : Attribute {
  public string Zuname;
  public int PersID;
  public DeveloperAttribute(string name) {
    Zuname = name;
  }
}

Der Konstruktor nimmt einen Parameter entgegen, nämlich den Wert für das Feld Zuname. Bevor Sie sich darüber Gedanken machen, wie man das Feld PersID initialisiert, sehen Sie sich an, wie das Attribut auf eine Klasse angewendet wird:


[DeveloperAttribute("Meier")] 
class ClassA {
  // Anweisungen
}

Mit dieser Definition wird der Konstruktor unter Übergabe einer Zeichenfolge aufgerufen. Das zweite Feld des Attributs (PersID) wird mit keinem bestimmten Wert initialisiert, es enthält 0. Selbstverständlich könnten wir innerhalb der Attributdefinition für PersID einen von 0 abweichenden Wert festlegen, aber dieser ist dann natürlich für jedes verknüpfte Element identisch.

Positionale und benannte Parameter

Um PersID einen individuellen Wert zuzuweisen, lässt sich DeveloperAttribute auch wie folgt an die Klasse heften:


[DeveloperAttribute("Meier", PersID = 8815)] 
class ClassA {
  // Anweisungen
}

Beachten Sie, dass wir jetzt zwei Argumente übergeben, obwohl der Konstruktor nur einen Parameter vorsieht. Dies ist ein besonderes Merkmal von Attributen, denn beim Initialisieren eines Attributs können Sie sowohl positionale als auch benannte Parameter verwenden.


Positionale Parameter sind die Parameter für den Konstruktoraufruf und müssen immer angegeben werden, wenn das Attribut gesetzt wird. Benannte Parameter sind optionale Parameter.

In unserem Beispiel ist Zuname ein positionaler Parameter, dem die Zeichenfolge »Meier« übergeben wird, während PersID ein benannter Parameter ist.

Benannte Parameter sind sehr flexibel. Einerseits können sie Standardwerte aufweisen, die grundsätzlich immer gültig sind, andererseits kann der Wert im Bedarfsfall individuell festgelegt werden. Ob Sie einen Datenmember positional oder benannt einsetzen wollen, ist die erste Entscheidung, die sich an den spezifischen Anforderungen orientiert. Ob ein benannter Parameter neu festgelegt werden muss oder der Standardwert akzeptabel ist, hängt vom Einzelfall ab.

Die Möglichkeit, benannte Parameter vorzusehen, befreit Sie von der Verpflichtung, für jede denkbare Kombination von Feldern und Eigenschaften überladene Konstruktoren in der Attributdefinition vorsehen zu müssen. Andererseits wird Ihnen damit aber nicht die Alternative entzogen, dennoch den Konstruktor zu überladen. Da unterscheiden sich die herkömmlichen Klassendefinitionen nicht von denen der Attribute.


Verknüpfen Sie ein Attribut mit einem Element und verwenden dabei positionale und benannte Parameter, müssen Sie eine wichtige Regel beachten: Zuerst werden die positionalen Parameter aufgeführt, danach die benannten.

Die Reihenfolge der benannten Parameter ist beliebig, da der Compiler aufgrund der Parameternamen richtig zuordnen kann. Benannte Parameter können alle öffentlich deklarierten Felder oder Eigenschaften sein – vorausgesetzt, sie sind weder statisch noch konstant definiert.

Die Daten eines Attributs auswerten

Ein Attribut soll einen steuernden Einfluss auf das Laufzeitverhalten eines Elements ausüben oder es mit wesentlichen Informationen versorgen. Enthält das Attribut Daten, ist es für das mit einem Attribut markierte Element wichtig, an die Daten des Attributs zu gelangen.

Im folgenden Codefragment soll dies für das Beispiel unseres eben entwickelten DeveloperAttributes gezeigt werden. Dazu schreiben wir den folgenden Code:

 

 


// -----------------------------------------------------------
// Beispiel: ...\Kapitel 7\DeveloperAttribute
// -----------------------------------------------------------
[DeveloperAttribute("Meier", PersID = 8815)]
class Program {
  static void Main(string[] args) {
    // Abrufen des Attributs
    DeveloperAttribute attr = 
            (DeveloperAttribute)Attribute.
             GetCustomAttribute(typeof(Program), 
             typeof(DeveloperAttribute));
    if (attr != null) {
      Console.WriteLine("Name: {0}", attr.Zuname);
      Console.WriteLine("PersID: {0}", attr.PersID);
    }
    else
      Console.WriteLine("Attribut nicht gesetzt");
    Console.ReadLine();
  }
}
// benutzerdefiniertes Attribut
[AttributeUsage(AttributeTargets.All)] 
public class DeveloperAttribute : Attribute {
  public string Zuname;
  public int PersID;
  public DeveloperAttribute (string name) {
    Zuname = name;
  }
}

An dieser Stelle sei darauf hingewiesen, dass wir in der Implementierung von Main davon ausgehen, dass nur unser benutzerdefiniertes Attribut DeveloperAttribute gesetzt ist – ein Element kann natürlich auch mit mehreren Attributen verknüpft sein. Im Code müssten wir dann die Klasse auf alle gesetzten Attribute abfragen.

Um die Daten eines Attributs auszuwerten, benötigen wir zunächst eine Referenz vom Typ des gesuchten Attributs, der wir das der Klasse angeheftete Attribut zuweisen. Mit den uns bisher bekannten Hilfsmitteln ist das allerdings nicht möglich, wir benötigen dazu eine Methode der Klasse Attribute, die uns die gewünschte Referenz liefert: Es ist die vielfach überladene Methode GetCustomAttribute.

In unserem Fall eignet sich die folgende Variante:


public static Attribute GetCustomAttribute(Module, Type);

Im ersten Argument geben wir den Typ des attributführenden Objekts, im zweiten Parameter den des Attributs an. Mit


DeveloperAttribute attr = (DeveloperAttribute)Attribute.GetCustomAttribute(typeof(   Class1), typeof(DeveloperAttribute));

erhalten wir nach entsprechender Typkonvertierung die Referenz auf das Attribut vom Typ DeveloperAttribute, das die Klasse Class1 ziert. Da wir nun die Referenz auf das Attribut in den Händen halten, könnten wir auf dessen öffentliche Member zugreifen. Um ganz sicherzugehen, dass das Attribut auch tatsächlich mit der Klasse verschmolzen ist, starten wir noch eine Abfrage (auf die wir allerdings in diesem Beispiel auch verzichten könnten), ob die Variable attr auch wirklich eine gültige Referenz enthält. Würde das Attribut nämlich nicht gefunden, wäre attr gleich null.


if (attr != null) {
  Console.WriteLine("Name: {0}", attr.Zuname);
  Console.WriteLine("PersID: {0}", attr.PersID);
}
else
  Console.WriteLine("Attribut nicht gesetzt");

An der Konsole wird damit


Name: Meier
PersID: 8815

ausgegeben. Wenn Sie das Beispiel in der Entwicklungsumgebung nachvollziehen, sollten Sie die Attributverknüpfung entfernen und die Anwendung nochmals starten. Das Attribut wird nicht gefunden und an der Konsole


Attribut nicht gesetzt

angezeigt.

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de